Compound Component Pattern을 이용한 컴포넌트 추상화
마지막 수정일: 2025. 05. 09.
문제상황
다음과 같은 search modal을 과거에 만든적이 있었다.
코드는 다음과 같다.
import { searchDocuments } from "@/api/meilisearch";
import { FileSvg, MagnifyingGlassSvg } from "./icons";
import { useState } from "react";
import Link from "next/link";
import { MDData } from "@/lib/types";
interface SearchResult extends MDData {
_formatted: MDData;
}
export default function SearchModal({
setIsOpen,
}: {
setIsOpen: (isOpen: boolean) => void;
}) {
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
async function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const searchQuery = event.target.value;
const results = await searchDocuments(searchQuery);
setSearchResults(results.hits as SearchResult[]);
}
return (
<div onClick={() => setIsOpen(false)}>
<Container onClick={(e) => e.stopPropagation()}>
<Input
type="text"
autoFocus
placeholder="Search docs..."
onChange={handleChange}
/>
<div className="flex flex-col gap-2.5">
{searchResults.length > 0 ? (
searchResults.map((result) => (
<SearchResultItem
key={result.id}
setIsOpen={setIsOpen}
result={result}
/>
))
) : (
<div className="flex items-center justify-center h-[100px]">
<span className="text-text text-lg">No results
</span>
</div>
)}
</div>
</Container>
</div>
);
}
굉장히 단순화한 버전이지만 응집도, 결합도, 추상화면에서 모두 문제가 존재하는 코드다. 심지어 여기에 키보드 입력으로 item을 선택하고 만들고 싶은데 문제가 더욱 심해질 듯 하였다.
응집도 : 컴포넌트의 구분 없이 한 컴포넌트에서 UI/상태관리/API를 모두 처리했다.
결합도 : setIsOpen의 depth가 높았으며 selected값이 들어올 경우 같이 증가할 것이 뻔했다.
추상화 : UI/API 로직이 같이 있어 SearchModal 자체에서 처리가 들어갔다.
해결방법
Compound Component를 사용해 문제를 해결하였다.
data, isopen, setisopen, query, onChange(setquery)
SearchModalFrame(isopen, setisopen) - 실제 api와 상태 작업(data)
data api로 받기
context 만들어서 provider로 제공 data, query "", onchange